package com.suisung.shopsuite.agent.componet.message.file;

import com.github.luben.zstd.ZstdDictCompress;
import com.suisung.shopsuite.agent.componet.message.util.KryoUtils;
import dev.langchain4j.data.message.ChatMessage;
import dev.langchain4j.data.message.ChatMessageType;
import dev.langchain4j.memory.ChatMemory;
import lombok.*;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;



@Component
@RequiredArgsConstructor
// 实现动态长度消息存储和自动空间管理。以下是完全重构的方案：
public class FileChatMemory implements ChatMemory {


    private final MsgCommitLog commitLog = MsgCommitLog.getINSTANCE();
    private final IndexQueue indexQueue = IndexQueue.getINSTANCE();
    private final ThreadLocal<UUID> uuidThreadLocal = new ThreadLocal<>();

    public void setUUID(UUID uuid){
        uuidThreadLocal.set(uuid);
    }

    @Override
    public Object id() {
        return uuidThreadLocal.get();
    }

    @Override
    public void add(ChatMessage chatMessage) {
        UUID id = (UUID) id();

        byte msgType = getMsgType(chatMessage.type());
        byte finishType = 0x1;

        byte[] data = KryoUtils.serialize(chatMessage);


        try {

            Long offset = commitLog.appendMsg(id, data, msgType, finishType);
            indexQueue.addIndex(id,offset);

        } catch (IOException e) {
            throw new RuntimeException(e);
        }

    }



    private byte getMsgType(ChatMessageType type){
        
        switch (type){
            
            
            
            
        }
        return 0x1;
 }
     @Override
    public List<ChatMessage> messages() {


         UUID id = (UUID) id();

         List<Long> indexes = null;
         try {
             indexes = indexQueue.getIndexes(id);
         } catch (IOException e) {
             throw new RuntimeException(e);
         }

         List<ChatMessage> collect = indexes.stream().map(x -> {

             try {
                 MsgCommitLog.WholeMsg wholeMsg = commitLog.readMsg(x);

                 return  (ChatMessage) KryoUtils.deserialize(wholeMsg.getData());



             } catch (IOException e) {
                 throw new RuntimeException(e);
             }

         }).collect(Collectors.toList());

         return collect.isEmpty() ? Collections.emptyList() : collect;

    }




    @Override
    public void clear() {

        UUID id = (UUID) id();
        IndexFile indexFile = indexQueue.getIndexFile(id);
        if(indexFile == null)return;
        indexFile.clearIndexFile(true,0);  // Clear All The Thing

    }




static class MsgCommitLog{


        @Getter
        private static MsgCommitLog INSTANCE;


        private final static Path fakePath = null;


       static {

           if(INSTANCE == null)INSTANCE = new MsgCommitLog(fakePath);

       }

    private final static int SIZE = 1024 * 1024;
        private final static int MSG_SIZE = 32;
        private  FileChannel fileChannel;
        private  MappedByteBuffer buffer;

        private long currentFileIndex = 0;
        private long currentOffset = 0;

       private final Path pathStore;

       private final String PATH_COMMITTAL = "commitlog.txt";
       private final static int HEADER_SIZE = 32;

       public MsgCommitLog(Path pathStore) {
           this.pathStore = pathStore;
           try {
               Files.createDirectories(pathStore);


           } catch (IOException e) {
               throw new RuntimeException(e);
           }
       }
        public void createStoreFile() throws IOException {
           if(buffer != null){
               buffer.force();
               fileChannel.close();
           }


            Path resolve = pathStore.resolve(PATH_COMMITTAL);

            this.fileChannel =  FileChannel.open(resolve, StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE);

            this.buffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, SIZE);

            currentFileIndex ++;
            currentOffset = 0;


        }

        public void init(){
        }

        public synchronized  Long appendMsg(UUID uuid , byte[] data, byte msgType , byte finishType) throws IOException {
        // 这里 写锁为了 防止出现一些问题 比如同时append的一些问题 主要是这个currentOffset出现问题
        int content_size = data.length + HEADER_SIZE;

        if(content_size + currentOffset > SIZE){

            createStoreFile();

        }

            ByteBuffer byteBuffer = ByteBuffer.allocate(HEADER_SIZE);
            byteBuffer.putLong(uuid.getMostSignificantBits());
            byteBuffer.putLong(uuid.getLeastSignificantBits());
            byteBuffer.putInt(content_size);   //注意这里是 总长度
            byteBuffer.put(msgType);
            byteBuffer.put(finishType);
            byteBuffer.putLong(System.currentTimeMillis());
            byteBuffer.put(new byte[2]);

            byteBuffer.flip();


            buffer.position((int) currentOffset);
            buffer.put(byteBuffer);
            buffer.put(data);

            long globalOffset = (currentFileIndex << 32) | currentOffset;
            currentOffset += content_size;

            return globalOffset;


        }



        public WholeMsg readMsg(long globalOffset) throws IOException {
           int globalIndex =(int) (globalOffset >> 32);

           int globalOffsetTrue =   (int) globalOffset  & 0x0000FFFF;


            try (FileChannel channel = FileChannel.open(
                    pathStore.resolve(String.format("%020d", globalIndex)),
                    StandardOpenOption.READ)) {

                // 读取消息头
                ByteBuffer header = ByteBuffer.allocate(32);
                channel.read(header, globalOffsetTrue);
                header.flip();

                // 读取消息体
                ByteBuffer body = ByteBuffer.allocate(MSG_SIZE);
                channel.read(body, globalOffsetTrue + 32);


                WholeMsg build = WholeMsg.builder()
                        .uuid(new UUID(header.getLong(), header.getLong()))
                        .content_size((long) header.getInt())
                        .msTpe(header.get())
                        .finishType(header.get())
                        .timestamp(header.getLong())
                        .remain(header.getChar())
                        .data(body.array())
                        .build();
                 header.clear();
                 body.clear();

                return build;
            }
//            ByteBuffer byteBuffer = ByteBuffer.allocate(HEADER_SIZE);
//            byteBuffer.putLong(uuid.getMostSignificantBits());
//            byteBuffer.putLong(uuid.getLeastSignificantBits());
//            byteBuffer.putInt(content_size);   //注意这里是 总长度
//            byteBuffer.put(msgType);
//            byteBuffer.put(finishType);
//            byteBuffer.putLong(System.currentTimeMillis());
//            byteBuffer.put(new byte[2]);

        }
        @Builder
        @Data
        static
        class WholeMsg{
           private UUID uuid;
           private Long content_size;
           private Long timestamp;
           private byte msTpe;
           private byte finishType;
           private char remain;
           private byte[] data;

        }

}

static   class IndexQueue{
        @Getter
        private  static IndexQueue INSTANCE ;
        private static final Path fakePath = null;
        static {
            INSTANCE  = new IndexQueue(fakePath);

        }

   public IndexQueue(Path pathStore) {
        this.pathStore = pathStore;
    }


    private final static  int  ENTRY_SIZE = 20;  // 20个字节
        private final Path pathStore;
        private final Map<UUID,IndexFile> kvQueue = new ConcurrentHashMap<>();



        public void addIndex(UUID uuid, long globalOffset){
//
//            computeIfAbsent()
//            语法：map.computeIfAbsent(key, k -> 生成值的逻辑)
//            行为：如果键不存在，会先执行 lambda 表达式生成值，再将 "键 - 值" 对放入 map；如果键已存在，直接返回现有值。
//            优势：生成值的逻辑（这里是new IndexFile(...)）只会在键不存在时执行，避免了无意义的对象创建。

//            putIfAbsent()
//            语法：map.putIfAbsent(key, 预先创建的值)
//            行为：需要先创建值对象，再放入 map。如果键已存在，会丢弃这个预先创建的值，返回现有值。
//            问题：无论键是否存在，new IndexFile(...)都会执行，可能导致无用的对象创建和资源浪费（比如打开文件句柄后又丢弃）。

            IndexFile indexFile = kvQueue.computeIfAbsent(uuid, x -> new IndexFile(pathStore.resolve(uuid.toString() + ".index")));

            indexFile.appendEntry(globalOffset, System.currentTimeMillis());

        }

        public List<Long> getIndexes(UUID conversationId) throws IOException {
            IndexFile indexFile = kvQueue.get(conversationId);
            if (indexFile == null) return Collections.emptyList();
            return indexFile.readAllOffsets();
        }

       private IndexFile getIndexFile(UUID conversationId){
           return kvQueue.get(conversationId);
       }


    }
static class IndexFile  {


        private final static int DELETE = 0x1111;
        private final static int SURVIVE = 0x2222;
        private final Path pathStore;
        private FileChannel fileChannel;
        private MappedByteBuffer mmapBuffer;
        private int writePosition = 0;
        @Getter
        private int size = 0;

        
        // 
        public IndexFile(Path path){

             this.pathStore = path;
            try {
                init();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }

        }


    private void init() throws IOException {

        if (!Files.exists(pathStore)) {
            Files.createDirectories(pathStore.getParent());
            Files.createFile(pathStore);
        }
           fileChannel = FileChannel.open(pathStore, StandardOpenOption.CREATE,StandardOpenOption.WRITE ,StandardOpenOption.READ);
        long size = Math.max(1024 * 1024, fileChannel.size()); // 最小1MB
         mmapBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, size);
         while (writePosition + IndexQueue.ENTRY_SIZE < mmapBuffer.capacity()) {
         long aLong = mmapBuffer.getLong(writePosition);
         long aLong2 = mmapBuffer.getLong(writePosition + 8);
         if(aLong == 0 && aLong2 == 0){break;}
         writePosition += IndexQueue.ENTRY_SIZE;
         }
         size = writePosition / IndexQueue.ENTRY_SIZE;
         
    }

    public synchronized void appendEntry(long globalOffset, long timestamp) {
            //这里按道理来说 是没有 必要的 但是由于jdk8的锁做了升级其实可以加防止 出错
            // 检查空间
                if (writePosition + IndexQueue.ENTRY_SIZE > mmapBuffer.capacity()) {
                    expand();
                }

                mmapBuffer.position(writePosition);
                mmapBuffer.putLong(globalOffset); //8
                mmapBuffer.putLong(timestamp);//8
                mmapBuffer.putInt(SURVIVE); //4    状态标志 目前默认状态  0x1111   0x2222
                writePosition += IndexQueue.ENTRY_SIZE;
                size ++;

    }

    private void expand() {
        try {

            long newSize = fileChannel.size() * 2;
            fileChannel.truncate(newSize);


            // 重新映射
            mmapBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, newSize);


        } catch (IOException e) {
            throw new RuntimeException("Failed to expand index file", e);
        }
    }

    public List<Long> readAllOffsets() {
        List<Long> offsets = new ArrayList<>();
        int position = 0;

        while (position < writePosition) {
            long offset = mmapBuffer.getLong(position);
            if (offset != 0) {
                offsets.add(offset);
            }
            position += IndexQueue.ENTRY_SIZE;
        }

        return offsets;
        }

    public void clearIndexFile(boolean isClearAll, Integer num){
     Integer max = writePosition / IndexQueue.ENTRY_SIZE;

        if(!isClearAll)max = num;
  Integer j = 0;
for (int i = writePosition-IndexQueue.ENTRY_SIZE  ; i  >= 0 && j <= max ; i-= IndexQueue.ENTRY_SIZE) {

   mmapBuffer.position(i + 8+8);
   if (mmapBuffer.getInt() == SURVIVE) {
       j ++ ;
       mmapBuffer.putInt(DELETE);
   }
}
}
    }

}
